mod solve;
mod solve_conflict;
mod solve_obsolete;
mod solve_require;
mod solve_term;
use std::{
    cmp::Ordering,
    collections::{BTreeMap, HashMap, HashSet, VecDeque},
    hash::Hash,
    sync::{
        atomic::{self, AtomicIsize},
        Arc,
    },
};

use crate::{
    cache::{
        installed::InstalledRepo,
        model::{EntryDetail, FileDetail},
        ver::Version,
    },
    repo::{CmdRepo, Repo},
};
use anyhow::{bail, Result};
use tokio::sync::Mutex;
use uuid::Uuid;
use varisat::{CnfFormula, ExtendFormula, Lit};
#[derive(Debug, Clone, Copy)]
pub enum TransationState {
    Installed,
    Upgrade,
    Downgrade,
    ToInstall,
    Obsolete(isize),
    Conflict(isize),
    // ConflictInstalled,
}

#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub(self) struct SolveItem {
    entry_name: Arc<String>,
    ver: Version,
    flag: Option<String>,
    // kind: EntryKind,
}
impl SolveItem {
    fn new(entry: EntryDetail) -> Self {
        let ver = Version::from_entry_detail(&entry);
        Self {
            entry_name: Arc::new(entry.entry_name),
            ver,
            flag: entry.flags,
        }
    }
    fn check_version(&self, ver: &Version, flag: &Option<String>) -> bool {
        Version::check_version(&self.ver, ver, &self.flag, flag)
    }
}

struct Assume {
    conflict_installed: Vec<isize>,
    necessary: Vec<isize>,
    installed: HashSet<isize>,
}
type PkgIndex = isize;
type EntryIndex = isize;
type TermIndex = isize;
#[derive(Debug, PartialEq, Eq, Clone, Hash, Copy)]
struct PkgIndexInfo {
    pkg_index: isize,
    arch_priority: u8,
    repo_priority: i64,
    // ver: Option<Version>,
}

impl Ord for PkgIndexInfo {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        match self.partial_cmp(other) {
            Some(ord) => ord,
            None => Ordering::Equal,
        }
    }
}
impl PartialOrd for PkgIndexInfo {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        match other.arch_priority.partial_cmp(&self.arch_priority) {
            Some(std::cmp::Ordering::Equal) => {}
            ord => return ord,
        }
        match other.repo_priority.partial_cmp(&self.repo_priority) {
            Some(std::cmp::Ordering::Equal) => {}
            ord => {
                return ord;
            }
        }
        match self.pkg_index.partial_cmp(&other.pkg_index) {
            Some(std::cmp::Ordering::Equal) => Some(std::cmp::Ordering::Equal),
            ord => {
                return ord;
            }
        }
        // self.ver.partial_cmp(&other.ver)
    }
}
#[derive(Clone)]
pub struct SolveContext {
    pub repos: Arc<Vec<Repo>>,
    pub installed_repo: InstalledRepo,
    pub cmd_repo: CmdRepo,
    formula: Arc<Mutex<CnfFormula>>,
    index_seq: Arc<AtomicIsize>,
    appeared: Arc<Mutex<HashSet<Uuid>>>,
    queue: Arc<Mutex<VecDeque<(isize, Uuid, RepoType)>>>,
    index_map_pkginfo: Arc<Mutex<BTreeMap<isize, SolvePkgInfo>>>,
    uuid_map_index: Arc<Mutex<HashMap<Uuid, isize>>>,
    entry_require: Arc<Mutex<HashMap<SolveItem, (EntryIndex, Vec<PkgIndexInfo>)>>>,

    index_map_entry: Arc<Mutex<BTreeMap<EntryIndex, SolveItem>>>,
    index_map_term: Arc<Mutex<BTreeMap<TermIndex, String>>>,
    arch_priority: HashMap<&'static str, u8>,
    repo_priority: HashMap<RepoType, i64>,
    assume: Arc<Mutex<Assume>>,
    same_pkg_name: Arc<Mutex<HashMap<String, HashSet<PkgIndex>>>>, // may be cause error
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub enum RepoType {
    Repo(Arc<String>),
    Installed,
    Cmd,
}
#[derive(Debug, Clone)]
struct SolvePkgInfo {
    state: TransationState,
    repo: RepoType,
    pkg_name: Arc<String>,
    pkg_uuid: Uuid,
    conflict: HashSet<isize>,
    obsolete: HashSet<isize>,
}
fn gen_arch_priority() -> HashMap<&'static str, u8> {
    let mut arch_priprity = HashMap::<&str, u8>::new();
    arch_priprity.insert("i386", 0);
    arch_priprity.insert("i686", 0);
    arch_priprity.insert("x86_64", 2);
    arch_priprity.insert("noarch", 3);
    arch_priprity
}
impl SolveContext {
    pub fn new(
        repos: Vec<Repo>,
        installed_repo: InstalledRepo,
        cmd_repo: CmdRepo,
        // arch: Option<String>,
    ) -> Self {
        let assume = Assume {
            conflict_installed: Vec::new(),
            necessary: Vec::new(),
            installed: HashSet::new(),
        };
        let mut repo_priority = HashMap::new();
        repo_priority.insert(RepoType::Cmd, std::i64::MAX);
        repo_priority.insert(RepoType::Installed, std::i64::MAX - 1);
        for ele in repos.iter() {
            repo_priority.insert(
                RepoType::Repo(ele.config.name.to_owned()),
                ele.config.base.priority,
            );
        }
        SolveContext {
            repos: Arc::new(repos),
            installed_repo,
            cmd_repo,
            formula: Arc::new(Mutex::new(CnfFormula::new())),
            appeared: Arc::new(Mutex::new(HashSet::new())),
            queue: Arc::new(Mutex::new(VecDeque::new())),
            index_map_pkginfo: Arc::new(Mutex::new(BTreeMap::new())),
            index_map_entry: Arc::new(Mutex::new(BTreeMap::new())),
            index_map_term: Arc::new(Mutex::new(BTreeMap::new())),
            uuid_map_index: Arc::new(Mutex::new(HashMap::new())),
            arch_priority: gen_arch_priority(),
            repo_priority,
            assume: Arc::new(Mutex::new(assume)),
            entry_require: Arc::new(Mutex::new(HashMap::new())),
            index_seq: Arc::new(AtomicIsize::new(1)),
            same_pkg_name: Arc::new(Mutex::new(HashMap::new())),
        }
    }

    pub async fn add_pkg_from_repo(&mut self, pkg_name: Arc<String>) -> Result<()> {
        //TODO maybe more than one repo have the same pkg_name,
        //let user choose which repo, for example aur yay
        //Default choose the repository with the highest priority.
        let (repo, pkg_uuid) = self
            .which_repo_provide_pkg(pkg_name.clone())
            .await?
            .pop()
            .unwrap();
        let repo = RepoType::Repo(repo.config.name.clone());
        dbg!(repo.clone());
        if self.check_appeared(pkg_uuid).await {
            return Ok(());
        };
        let pkg_index = self
            .add_pkg_info(
                pkg_name.clone(),
                pkg_uuid,
                repo.clone(),
                TransationState::ToInstall,
            )
            .await;
        // let mut assume_lock = self.assume.lock().await;
        // assume_lock.necessary.push(pkg_index);
        // drop(assume_lock);
        self.formula
            .lock()
            .await
            .add_clause(&vec![Lit::from_dimacs(pkg_index)]);

        self.add_pkg(pkg_index, pkg_uuid, repo).await?;
        Ok(())
    }
    pub async fn add_pkg_from_cmd(&mut self, pkg_infos: Vec<(Uuid, String)>) -> Result<()> {
        for (pkg_uuid, pkg_name) in pkg_infos {
            if !self.check_appeared(pkg_uuid).await {
                let pkg_index = self
                    .add_pkg_info(
                        Arc::new(pkg_name),
                        pkg_uuid,
                        RepoType::Cmd,
                        TransationState::ToInstall,
                    )
                    .await;
                self.add_pkg(pkg_index, pkg_uuid, RepoType::Cmd).await?;
            }
        }
        Ok(())
    }
    async fn add_pkg(&mut self, pkg_index: isize, pkg_uuid: Uuid, repo: RepoType) -> Result<()> {
        self.add_queue(pkg_index, pkg_uuid, repo).await;
        while let Some(r) = self.pop_queue().await {
            self.solve_require(r.clone()).await?;
            self.solve_conflict(r.clone()).await?;
            self.solve_obsolete(r).await?;
        }
        Ok(())
    }
    async fn which_repo_provide_pkg(&self, pkg_name: Arc<String>) -> Result<Vec<(Repo, Uuid)>> {
        let mut handles = Vec::new();
        let pkg_name = Arc::new(pkg_name.to_string());
        for repo in self.repos.iter() {
            handles.push((
                repo,
                repo.data.clone().get_pkg_uuid_by_pkg_name(pkg_name.clone()),
            ));
        }

        let mut res = Vec::new();
        for (repo, handle) in handles {
            if let Some(pkg_uuid) = handle {
                res.push((repo.clone(), pkg_uuid));
            }
        }
        if res.is_empty() {
            bail!("No repo provides the pkg {}", pkg_name);
        }
        res.sort_by(|a, b| a.0.config.base.priority.cmp(&b.0.config.base.priority));
        Ok(res)
    }

    async fn which_repo_pkg_provide_entry(
        &self,
        entry_name: &str,
    ) -> Result<Option<Vec<(RepoType, Vec<(Uuid, EntryDetail)>)>>> {
        let mut handles = Vec::new();
        let entry = Arc::new(entry_name.to_string());
        for repo in self.repos.iter() {
            handles.push((
                RepoType::Repo(repo.config.name.clone()),
                tokio::spawn(
                    repo.data
                        .clone()
                        .get_what_pkg_provide_entry_by_name(entry.clone()),
                ),
            ));
        }
        let mut provides = Vec::new();
        for (repo, handle) in handles {
            if let Some(provide) = handle.await? {
                provides.push((repo, provide));
            }
        }
        if provides.is_empty() {
            Ok(None)
        } else {
            Ok(Some(provides))
        }
    }
    async fn which_repo_pkg_provide_file(
        &self,
        file_path: &str,
    ) -> Result<Option<Vec<(RepoType, Vec<(Uuid, FileDetail)>)>>> {
        let mut handles = Vec::new();
        let file_path = Arc::new(file_path.to_string());
        for repo in self.repos.iter() {
            handles.push((
                RepoType::Repo(repo.config.name.clone()),
                tokio::spawn(
                    repo.data
                        .clone()
                        .get_what_pkg_provide_file_by_path(file_path.clone()),
                ),
            ));
        }
        let mut provides = Vec::new();
        for (repo, handle) in handles {
            if let Some(provide) = handle.await? {
                provides.push((repo, provide));
            }
        }
        if provides.is_empty() {
            Ok(None)
        } else {
            Ok(Some(provides))
        }
    }
    async fn installed_try_update_or_downgrade(
        &mut self,
        pkg_name: Arc<String>,
        item: &mut SolveItem,
        installed_ver: &Version,
    ) -> Result<bool> {
        let mut res = false;
        if let Ok(s) = self.which_repo_provide_pkg(pkg_name.clone()).await {
            let mut handles = Vec::new();
            for (repo, pkg_uuid) in s {
                handles.push((
                    RepoType::Repo(repo.config.name.clone()),
                    pkg_uuid,
                    tokio::spawn(repo.data.get_pkg_detail_by_uuid(pkg_uuid)),
                ));
            }

            let mut indexs = Vec::new();
            for (repo, pkg_uuid, handle) in handles {
                if let Some(pkg_detail) = handle.await? {
                    if !item.check_version(&pkg_detail.version, &Some("EQ".to_string())) {
                        let state = match pkg_detail.version.partial_cmp(installed_ver) {
                            Some(Ordering::Greater) => TransationState::Upgrade,
                            Some(Ordering::Less) => TransationState::Downgrade,
                            _ => {
                                continue;
                            }
                        };
                        let index = self
                            .add_pkg_info(pkg_name.clone(), pkg_uuid, repo, state)
                            .await;
                        indexs.push(index);
                    };
                }
            }
            if !indexs.is_empty() {
                res = true;
                self.update_appeared(&indexs).await;
            }
        }
        Ok(res)
    }
    fn get_latest_index(&self) -> isize {
        // self.global_index_seq.fetch_add(1, atomic::Ordering::SeqCst)
        self.index_seq.fetch_add(1, atomic::Ordering::SeqCst)
    }
    fn get_arch_priority(&self, arch: &str) -> u8 {
        match self.arch_priority.get(arch) {
            Some(s) => *s,
            None => 0,
        }
    }
    fn get_repo_priority(&self, repo: &RepoType) -> i64 {
        match self.repo_priority.get(&repo) {
            Some(s) => *s,
            None => std::i64::MIN,
        }
    }
    async fn check_appeared(&mut self, pkg_uuid: Uuid) -> bool {
        let mut appeared_lock = self.appeared.lock().await;
        let r = match appeared_lock.get(&pkg_uuid) {
            Some(_) => true,
            None => {
                appeared_lock.insert(pkg_uuid);
                false
            }
        };
        drop(appeared_lock);
        r
    }
    async fn add_queue(&mut self, index: isize, pkg_uuid: Uuid, repo: RepoType) {
        let mut queue_lock = self.queue.lock().await;
        queue_lock.push_back((index, pkg_uuid, repo));
        drop(queue_lock);
    }
    async fn pop_queue(&mut self) -> Option<(isize, Uuid, RepoType)> {
        let mut queue_lock = self.queue.lock().await;
        let r = queue_lock.pop_front();
        drop(queue_lock);
        r
    }
    async fn add_pkg_info(
        &mut self,
        pkg_name: Arc<String>,
        pkg_uuid: Uuid,
        repo: RepoType,
        state: TransationState,
    ) -> PkgIndex {
        let mut uuid_map_index_lock = self.uuid_map_index.lock().await;
        let mut index_map_pkginfo_lock = self.index_map_pkginfo.lock().await;
        let index = match uuid_map_index_lock.get(&pkg_uuid) {
            Some(index) => {
                match state {
                    TransationState::Conflict(i) => {
                        if let Some(pkg_info) = index_map_pkginfo_lock.get_mut(index) {
                            pkg_info.conflict.insert(i);
                        }
                    }
                    TransationState::Obsolete(i) => {
                        if let Some(pkg_info) = index_map_pkginfo_lock.get_mut(index) {
                            pkg_info.obsolete.insert(i);
                        }
                    }
                    _ => {}
                }
                index.to_owned()
            }
            None => {
                let index = self.get_latest_index();
                uuid_map_index_lock.insert(pkg_uuid, index);
                let mut conflict = HashSet::new();
                let mut obsolete = HashSet::new();
                match state {
                    TransationState::Conflict(i) => {
                        conflict.insert(i);
                    }
                    TransationState::Obsolete(i) => {
                        obsolete.insert(i);
                    }
                    _ => {}
                }
                index_map_pkginfo_lock.insert(
                    index,
                    SolvePkgInfo {
                        state,
                        pkg_name,
                        pkg_uuid,
                        repo,
                        conflict,
                        obsolete,
                    },
                );
                index
            }
        };
        drop(index_map_pkginfo_lock);
        drop(uuid_map_index_lock);

        return index;
    }
    async fn update_appeared(&mut self, indexs: &Vec<PkgIndex>) {
        let map_lock = self.index_map_pkginfo.lock().await;
        let mut appeared_lock = self.appeared.lock().await;
        let mut queue_lock = self.queue.lock().await;
        for index in indexs {
            match map_lock.get(&index) {
                Some(pkg_info) => match pkg_info.state {
                    TransationState::ToInstall
                    | TransationState::Downgrade
                    | TransationState::Upgrade => {
                        if !appeared_lock.contains(&pkg_info.pkg_uuid) {
                            appeared_lock.insert(pkg_info.pkg_uuid);
                            queue_lock.push_back((
                                *index,
                                pkg_info.pkg_uuid,
                                pkg_info.repo.clone(),
                            ));
                        }
                    }
                    _ => {}
                },
                None => {}
            }
        }
    }
    async fn update_installed(&mut self, pkg_index: isize) {
        let mut assume_lock = self.assume.lock().await;
        assume_lock.installed.insert(pkg_index);
        drop(assume_lock);
    }
}
// async fn is_provide_pkg(repo_data: RepoData, pkg_name: String) -> Option<Uuid> {
//     repo_data.get_pkg_uuid_by_pkg_name(pkg_name.as_str()).await
// }

// pub async fn get_formulat_by_package(
//     repos: Vec<Repo<>>,
//     installed_repo: InstalledRepo,
// ) -> Result<()> {
//     Ok(())
// }
